//===============================================================================
// Microsoft patterns & practices
// Mobile Client Software Factory - July 2006
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.Text;
using System.Reflection;
using Microsoft.Cci;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Mobile.ObjectBuilder;

namespace Microsoft.Practices.ObjectBuilder.ObGen
{
	[ConfigurationElementType(typeof(PolicyGeneratorFactoryElement<MethodPolicyGenerator>))]
	public class MethodPolicyGenerator : MethodBaseDependencyPolicyGenerator
	{
		private static readonly CodeTypeReference ObjectArrayType = new CodeTypeReference(new CodeTypeReference(typeof(object)), 1);

		public override CodeStatementCollection GenerateStatements(Type typeToProcess, CodeTypeDeclaration generatedType, NamingContext namingContext)
		{
			TypeNode metadataType = MetadataCache.GetTypeMetadata(typeToProcess);
			base.AddAssemblyReference(typeToProcess.Assembly);
			base.AddNamespaceImport(typeof(MethodPolicy).Namespace);
			base.AddNamespaceImport(typeof(StrongTypedMethodCallInfo).Namespace);
			base.AddNamespaceImport(typeof(MethodInvokeHandler).Namespace);
			base.AddNamespaceImport(typeof(IMethodCallInfo).Namespace);
			
			// We generate the pair [MethodName]Invoke and [MethodName]GetParameters 
			// for each injection method. These methods are passed-in to the 
			// constructor of the StrongTypedMethodCallInfo object which is 
			// in turn added to the MethodPolicy.Methods list.

			CodeStatementCollection statements = new CodeStatementCollection();

			// MethodPolicy policy = new MethodPolicy();
			CodeVariableDeclarationStatement policy = new CodeVariableDeclarationStatement(
				typeof(MethodPolicy).Name, namingContext.GetVariableName("policy"),
				new CodeObjectCreateExpression(typeof(MethodPolicy).Name));
			statements.Add(policy);

			foreach (MethodInfo method in typeToProcess.GetMethods())
			{
				if (!method.IsAbstract && !method.IsConstructor && !method.IsSpecialName && 
					Attribute.IsDefined(method, typeof(InjectionMethodAttribute)))
				{
					Method methodMetadata = MetadataCache.GetMethodMetadata(method);

					CodeMemberMethod invokeMethod = GenerateMethodInvoke(typeToProcess, namingContext, method);
					generatedType.Members.Add(invokeMethod);
					CodeMemberMethod getParameters = GenerateGetParametersMethod(
						namingContext.GetMemberName(method.Name + "GetParameters"),
						methodMetadata.Parameters);
					generatedType.Members.Add(getParameters);

					//new StrongTypedMethodCallInfo(
					//	new MethodInvocationHandler([MethodName]Invoke)
					//	new MethodParametersHandler([MethodName]GetParameters)
					//	typeof(object));
					CodeObjectCreateExpression createCallInfo = new CodeObjectCreateExpression(
						typeof(StrongTypedMethodCallInfo).Name,
						new CodeDelegateCreateExpression(
							new SafeCodeTypeReference(typeof(MethodInvokeHandler)),
							new CodeTypeReferenceExpression(generatedType.Name),
							invokeMethod.Name
						),
						new CodeDelegateCreateExpression(
							new SafeCodeTypeReference(typeof(MethodParametersHandler)),
							new CodeTypeReferenceExpression(generatedType.Name),
							getParameters.Name
						)
					);

					List<string> paramTypes = new List<string>();
					foreach (ParameterInfo paramInfo in method.GetParameters())
					{
						createCallInfo.Parameters.Add(new CodeTypeOfExpression(paramInfo.ParameterType));
						paramTypes.Add(paramInfo.ParameterType.Name);						
					}

					statements.AddRange(base.GenerateBlockComment(String.Format(
						CultureInfo.CurrentCulture, 
						Properties.Resources.MethodComment, 
						typeToProcess.Name, 
						method.Name, 
						String.Join(", ", paramTypes.ToArray()))));

					//IMethodCallInfo callInfo = new StrongTypedMethodCallInfo(...);
					CodeVariableDeclarationStatement callInfo = new CodeVariableDeclarationStatement(
						typeof(IMethodCallInfo).Name,
						namingContext.GetVariableName("callInfo"),
						createCallInfo);
					statements.Add(callInfo);
					//policy.Methods.Add("SetValue", callInfo);
					statements.Add(
						new CodeMethodInvokeExpression(
							new CodePropertyReferenceExpression(
								new CodeVariableReferenceExpression(policy.Name),
								"Methods"
							),
							"Add",
							new CodePrimitiveExpression(method.Name), 
							new CodeVariableReferenceExpression(callInfo.Name)
						)
					);
				}
			}

			// We need to *always* inject the IMethodPolicy, even if no 
			// injection method was found, so that the reflection-based 
			// method strategy does not reflect over the type again.

			//ctx.Policies.Set<IMethodPolicy>(policy, typeof(MockObject), null);
			statements.Add(base.SetPolicyStatement(typeof(IMethodPolicy), new CodeVariableReferenceExpression(policy.Name)));

			return statements;
		}

		private static CodeMemberMethod GenerateMethodInvoke(Type typeToProcess, NamingContext namingContext, MethodInfo method)
		{
			CodeMemberMethod invokeMethod = new CodeMemberMethod();
			// Method overloads will get a number appended at the end.
			invokeMethod.Attributes = MemberAttributes.Private | MemberAttributes.Static;
			invokeMethod.Name = namingContext.GetMemberName(method.Name + "Invoke");
			invokeMethod.ReturnType = new SafeCodeTypeReference(typeof(object));
			invokeMethod.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "target"));
			invokeMethod.Parameters.Add(new CodeParameterDeclarationExpression(ObjectArrayType, "parameters"));

			//((Mock)target).Foo((int)parameters[0]);
			CodeMethodInvokeExpression call = new CodeMethodInvokeExpression(
				new CodeCastExpression(typeToProcess, new CodeArgumentReferenceExpression("target")),
				method.Name);
			ParameterInfo[] parameters = method.GetParameters();
			for (int i = 0; i < parameters.Length; i++)
			{
				ParameterInfo paramInfo = parameters[i];
				// (int)parameters[0]
				call.Parameters.Add(
					new CodeCastExpression(
						paramInfo.ParameterType,
						new CodeArrayIndexerExpression(
							new CodeArgumentReferenceExpression("parameters"),
							new CodePrimitiveExpression(i)
						)
					)
				);
			}

			if (method.ReturnType != typeof(void))
			{
				// return [methodInvoke];
				invokeMethod.Statements.Add(new CodeMethodReturnStatement(call));
			}
			else
			{
				// [methodInvoke];
				// return null;
				invokeMethod.Statements.Add(call);
				invokeMethod.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(null)));
			}
			return invokeMethod;
		}
	}
}
